package com.lyncs.ods.modules.goods.service.impl;

import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.map.MapUtil;
import cn.hutool.core.util.IdUtil;
import com.alibaba.fastjson.JSONObject;
import com.baomidou.mybatisplus.extension.conditions.query.LambdaQueryChainWrapper;
import com.google.common.collect.MapDifference;
import com.google.common.collect.Maps;
import com.lyncs.ods.common.api.CommonPage;
import com.lyncs.ods.common.api.Option;
import com.lyncs.ods.common.exception.ApiException;
import com.lyncs.ods.common.web.RequestHolder;
import com.lyncs.ods.constant.LyncsOdsConstant;
import com.lyncs.ods.modules.company.model.CompanySpuRelation;
import com.lyncs.ods.modules.company.service.CompanySpuRelationService;
import com.lyncs.ods.modules.goods.model.*;
import com.lyncs.ods.modules.goods.service.*;
import com.lyncs.ods.req.ListSkuReq;
import com.lyncs.ods.req.SaveSkuReq;
import com.lyncs.ods.resp.SpuAttrInfoResp;
import com.lyncs.ods.resp.SpuInfoResp;
import com.lyncs.ods.utils.CurrencyUtil;
import com.lyncs.ods.utils.PageUtil;
import com.lyncs.ods.utils.SkuUtils;
import com.lyncs.ods.utils.SpuUtils;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.collections4.MapUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.math.BigDecimal;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.function.Function;
import java.util.stream.Collectors;
import java.util.stream.Stream;

/**
 * @author alex
 * @date 2022/2/5 21:28
 * @description
 */
@Service
@Transactional(rollbackFor = Exception.class)
public class GoodServiceImpl implements GoodService {

    @Autowired
    private SpuDetailService spuDetailService;
    @Autowired
    private SpuInfoService spuInfoService;
    @Autowired
    private SkuInfoService skuInfoService;
    @Autowired
    private SkuDetailService skuDetailService;
    @Autowired
    private SkuAttrLogService skuAttrLogService;
    @Autowired
    private CompanySpuRelationService companySpuRelationService;

    /**
     * get all available spu
     *
     * @return spu info
     */
    @Override
    public CommonPage<SpuInfoResp> listSpu(String keyword, Integer page, Integer pageSize) {
        LambdaQueryChainWrapper<SpuInfo> wrapper = spuInfoService.lambdaQuery().like(StringUtils.isNotEmpty(keyword), SpuInfo::getName, keyword).eq(SpuInfo::getStatus, LyncsOdsConstant.SPUStatus.ENABLE.getKey());
        CommonPage<SpuInfoResp> commonPage = PageUtil.count(wrapper, page, pageSize);
        if (commonPage.getTotal() == 0) {
            return commonPage;
        }
        List<CompanySpuRelation> companySpuRelations = companySpuRelationService.lambdaQuery().eq(CompanySpuRelation::getCompanyId, RequestHolder.getCompanyId()).eq(CompanySpuRelation::getStatus, LyncsOdsConstant.EnableStatus.ENABLE.getKey()).list();
        List<Long> relatedSpuIds = companySpuRelations.stream().map(CompanySpuRelation::getSpuId).distinct().collect(Collectors.toList());
        List<SpuInfo> spuInfos = wrapper.last(PageUtil.getLastSql(page, pageSize)).list();
        List<Long> spuIds = spuInfos.stream().map(SpuInfo::getId).collect(Collectors.toList());
        List<SpuDetail> spuDetails = spuDetailService.lambdaQuery()
                .in(SpuDetail::getSpuId, spuIds)
                .eq(SpuDetail::getStatus, LyncsOdsConstant.EnableStatus.ENABLE.getKey())
                .list();
        Map<Long, Map<Long, String>> attrMap = spuDetails.stream().collect(Collectors.groupingBy(SpuDetail::getSpuId, Collectors.toMap(SpuDetail::getId, SpuUtils::getAttrName)));
        Function<SpuInfo, SpuInfoResp> func = (info) -> new SpuInfoResp().setSpuId(info.getId()).setName(SpuUtils.getName(info)).setRelated(relatedSpuIds.contains(info.getId())).setSkuAttrs(attrMap.get(info.getId()));
        return PageUtil.result(spuInfos, func, commonPage);
    }

    /**
     * get spu attr info by id
     *
     * @param spuId spu id
     * @return attr infos
     */
    @Override
    public List<SpuAttrInfoResp> getSpuAttrInfos(Long spuId) {
        List<SpuDetail> attrs = spuDetailService.lambdaQuery().eq(SpuDetail::getSpuId, spuId).eq(SpuDetail::getStatus, LyncsOdsConstant.SPUStatus.ENABLE.getKey()).list();
        return attrs.stream().map(info -> new SpuAttrInfoResp()
                .setAttrId(info.getId())
                .setSpuId(info.getSpuId())
                .setName(SpuUtils.getAttrName(info))
                .setKey(info.getKey())).collect(Collectors.toList());
    }

    /**
     * batch save sku
     *
     * @param req request info
     * @return sku id
     */
    @Override
    public Long saveSku(SaveSkuReq req) {
        Long spuId = req.getSpuId() == null ? 0L : req.getSpuId();
        SkuInfo skuInfo;
        Long skuId = req.getSkuId();
        if (skuId == null) {
            checkExist(req);
            skuId = IdUtil.getSnowflakeNextId();
            skuInfo = new SkuInfo()
                    .setId(skuId)
                    .setSpuId(spuId)
                    .setCompanyId(RequestHolder.getCompanyId());
        } else {
            skuInfo = skuInfoService.lambdaQuery().eq(SkuInfo::getId, req.getSkuId()).one();
            if (Objects.isNull(skuInfo) || !LyncsOdsConstant.SPUStatus.ENABLE.getKey().equals(skuInfo.getStatus())) {
                throw new ApiException("can not update sku for not exist or disabled.");
            }
            //删除旧属性再新增
            skuDetailService.lambdaUpdate().eq(SkuDetail::getSkuId, req.getSkuId()).remove();
        }
        skuInfo.setName(req.getName())
                .setImgUrl(req.getImgUrl())
                .setAmount(req.getAmount())
                .setStatus(LyncsOdsConstant.SKUStatus.ENABLE.getKey())
                .setUnit(req.getUnit())
                .setCurrency(req.getCurrency())
                .setAmountJson(JSONObject.toJSONString(req.getAmountJson()))
                .setNo(req.getInnerCode());
        skuInfoService.saveOrUpdate(skuInfo);
        if (CollectionUtils.isNotEmpty(req.getAttrs())) {
            List<SkuDetail> skuDetails = req.getAttrs().stream().map(attr -> {
                SkuDetail skuDetail = new SkuDetail();
                skuDetail.setId(IdUtil.getSnowflakeNextId())
                        .setSkuId(skuInfo.getId())
                        .setSpuId(spuId)
                        .setName(attr.getName())
                        .setAttrId(attr.getAttrId())
                        .setValue(attr.getValue())
                        .setStatus(LyncsOdsConstant.SKUStatus.ENABLE.getKey());
                return skuDetail;
            }).collect(Collectors.toList());
            skuDetailService.saveOrUpdateBatch(skuDetails);
        }
        return skuId;
    }

    private void checkExist(SaveSkuReq req) {
        if (req.getSpuId() == null || req.getSpuId() == 0) {
            return;
        }
        List<SkuDetailInfo> skuInfoResp = listSkuInfo(req.getSpuId());
        if (CollectionUtil.isEmpty(skuInfoResp) || CollectionUtils.isEmpty(req.getAttrs())) {
            return;
        }
        Map<String, String> newSkuAttrMap = req.getAttrs().stream().collect(Collectors.toMap(SaveSkuReq.AttrInfo::getName, SaveSkuReq.AttrInfo::getValue));

        Map<Long, Map<String, String>> skuAttrMap = skuInfoResp.stream().filter(i -> MapUtils.isNotEmpty(i.getSkuAttrs())).collect(Collectors.toMap(SkuDetailInfo::getSkuId, SkuDetailInfo::getSkuAttrs));

        skuAttrMap.forEach((skuId, attrMap) -> {
            MapDifference<String, String> mapDifference = Maps.difference(newSkuAttrMap, attrMap);
            // entriesInCommon：返回两个map中key和value都相同的记录；
            // entriesOnlyOnLeft：返回左边Map独有的记录；
            // entriesOnlyOnRight：返回右边Map独有的记录；
            // entriesDiffering：返回两个map中key相同value不同的记录.
            //areEqual：返回两个map是否完全相同
            if (mapDifference.areEqual()) {
                throw new ApiException("sku has already exist.");
            }
        });
    }

    /**
     * get sku info by spu id
     *
     * @param req request info
     * @return sku infos
     */
    @Override
    public CommonPage<SkuDetailInfo> listSkuInfo(ListSkuReq req) {
        /*if (spuId != null && spuId != 0) {
            saveAttrLog(req);
        }*/
        return skuInfoService.getSkuDetailInfos(req.getSpuId(), RequestHolder.getCompanyId(), req.getKeyword(), req.getCurrency(), req.getAttrMap(), req.getPage(), req.getPageSize(), req.getSource());
    }

    @Override
    public List<String> listSpuOptions(Long spuId, Long attrId) {
        List<SkuInfo> skuInfos = skuInfoService.lambdaQuery().eq(SkuInfo::getCompanyId, RequestHolder.getCompanyId()).eq(SkuInfo::getSpuId, spuId).list();
        if (CollectionUtils.isEmpty(skuInfos) || attrId == null) {
            return null;
        }
        if (attrId == 0L) {
            return skuInfos.stream().map(SkuInfo::getUnit).distinct().collect(Collectors.toList());
        }
        List<Long> skuIds = skuInfos.stream().map(SkuInfo::getId).collect(Collectors.toList());
        List<SkuDetail> skuDetails = skuDetailService.lambdaQuery().in(SkuDetail::getSkuId, skuIds).eq(SkuDetail::getAttrId, attrId).list();
        return skuDetails.stream().map(SkuDetail::getValue).distinct().collect(Collectors.toList());
    }

    @Override
    public void deleteSku(Long skuId) {
        skuInfoService.lambdaUpdate().eq(SkuInfo::getId, skuId).set(SkuInfo::getStatus, LyncsOdsConstant.EnableStatus.DISABLE.getKey()).update();
    }

    private List<SkuDetailInfo> listSkuInfo(Long spuId) {
        return skuInfoService.getSkuDetailInfos(spuId, RequestHolder.getCompanyId(), null, null, null, null, null, null).getList();
    }

    private BigDecimal getAmount(String fromCurrency, String toCurrency, String amountStr, BigDecimal amount) {
        if (Objects.isNull(fromCurrency) || Objects.isNull(toCurrency) || fromCurrency.equals(toCurrency)) {
            return amount;
        }
        JSONObject amountJson = JSONObject.parseObject(amountStr);
        if (amountJson.containsKey(toCurrency)) {
            return CurrencyUtil.fen2yuan(amountJson.getBigDecimal(toCurrency));
        }
        return CurrencyUtil.exchange(fromCurrency, toCurrency, amount, Boolean.TRUE);
    }

    @Override
    public List<SkuDetailInfo> getSkuDetailInfos(List<Long> skuIds) {
        List<SkuInfo> skuInfos = skuInfoService.lambdaQuery().in(SkuInfo::getId, skuIds).list();
        Map<Long, SkuInfo> skuInfoMap = skuInfos.stream().collect(Collectors.toMap(SkuInfo::getId, o -> o, (o1, o2) -> o1));
        List<SkuDetail> skuDetails = skuDetailService.lambdaQuery().in(SkuDetail::getSkuId, skuIds).list();
        Map<Long, Map<String, String>> skuAttrMap = skuInfoService.getSkuAttrs(skuDetails);
        return skuIds.stream().map(id -> {
            SkuInfo skuInfo = skuInfoMap.get(id);
            SkuDetailInfo skuDetailInfo = new SkuDetailInfo();
            BeanUtils.copyProperties(skuInfo, skuDetailInfo);
            skuDetailInfo.setSkuAttrs(skuAttrMap.get(id));
            return skuDetailInfo;
        }).collect(Collectors.toList());
    }

    @Override
    public List<Option<Long, BigDecimal>> refreshSkuAmount(String skuIds, String currency) {
        if (StringUtils.isEmpty(skuIds) || StringUtils.isEmpty(currency)) {
            return null;
        }
        List<Long> skuIdList = Stream.of(skuIds.split(LyncsOdsConstant.Common.COMMA)).map(Long::valueOf).collect(Collectors.toList());
        List<SkuInfo> skuInfos = skuInfoService.lambdaQuery()
                .in(SkuInfo::getId, skuIdList)
                .eq(SkuInfo::getCompanyId, RequestHolder.getCompanyId())
                .eq(SkuInfo::getStatus, LyncsOdsConstant.SKUStatus.ENABLE.getKey())
                .list();
        return skuInfos.stream().map(sku -> new Option<Long, BigDecimal>().setKey(sku.getId()).setValue(SkuUtils.toCurrencyAmount(sku, currency))).collect(Collectors.toList());
    }

    @Override
    public SkuDetailInfo getSkuDetail(Long skuId, String currency) {
        SkuInfo skuInfo = skuInfoService.lambdaQuery().eq(SkuInfo::getId, skuId).eq(SkuInfo::getCompanyId, RequestHolder.getCompanyId()).one();
        if (skuInfo == null || LyncsOdsConstant.EnableStatus.DISABLE.getKey().equals(skuInfo.getStatus())) {
            throw new ApiException("sku不存在或已删除");
        }
        SpuInfo spuInfo = new SpuInfo();
        if (skuInfo.getSpuId() != 0) {
            spuInfo = spuInfoService.lambdaQuery().eq(SpuInfo::getId, skuInfo.getSpuId()).one();
            if (spuInfo == null) {
                throw new ApiException("spu不存在或已删除");
            }
        }
        List<SkuDetail> skuDetails = skuDetailService.lambdaQuery().eq(SkuDetail::getSkuId, skuId).list();
        SkuDetailInfo skuDetailInfo = new SkuDetailInfo();
        BeanUtils.copyProperties(skuInfo, skuDetailInfo);
        Map<String, String> attrMap = skuInfoService.getSkuAttrs(skuDetails).get(skuId);
        skuDetailInfo.setSkuAttrs(attrMap);
        skuDetailInfo.setSkuId(skuId)
                .setInnerCode(skuInfo.getNo())
                .setTransCurrency(currency)
                .setTransAmount(SkuUtils.toCurrencyAmount(skuInfo, currency))
                .setSpuName(spuInfo.getName());
        return skuDetailInfo;
    }
}
